#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <err.h>
#include "base64.h"

const char* errmsg[]={	"I/O error", 
						"Error de padding, si el tercer caracter es '=', el cuarto debe serlo.\n", "error2",
						"Caracter invalido (ASCII %d) %c\n"
};

int doDecode(int source[], char decoded[], int amount)
{
	int i,padding,mapped[4];
	padding=0;

	for(i=0;i<4;i++){
		if(source[i]>=65 && source[i]<=90 ){
			mapped[i] = (source[i]-65);
		} else if(source[i] >= 97 && source[i] <= 122 ) {
			mapped[i] = (source[i]-97+26);			
		} else if(source[i]>=48 && source[i]<=57){
			mapped[i] = (source[i]-48+52);	
		} else if(source[i]==43){
			mapped[i] = 62;//'+'
		} else if(source[i]==47){
			mapped[i] = 63;//'/'
		} else if(source[i]==61 && (i==2)){
			if(source[i+1]!=61){
				//error si el tercer byte es padding, el cuarto tiene que serlo
//				fprintf(stderr, "Error de padding, si el tercer caracter es '=', el cuarto debe serlo.\n");
				return -2;
			}
			padding++;		
		} else if(source[i]==61 && (i==3)){
			padding++;		
		} else {
//			fprintf(stderr, "Caracter invalido (ASCII %d %d %d %d) %d\n", source[0], source[1], source[2], source[3], i);
			return -3;
		}
	}

	// seteo todo temp en 0
	for(i=0; i < amount; i++){ 
		decoded[i]=0;
	}
	
	decoded[0] = mapped[0] << 2;
	decoded[0] |= mapped[1] >> 4;

	decoded[1] = mapped[1] << 4;
	decoded[1] |= mapped[2] >> 2;

	//posiciono los ultimos 4 bits de source[1] en las posiciones 2, 3, 4 y 5 de encoded[2]
	decoded[2] = (mapped[2] << 6); 
	decoded[2] |= mapped[3];
	
	
	return 3-padding;
}

FILE* getInFP(int fdin)
{
      FILE *fp = fdopen(fdin, "rb");
  
      if (fp == NULL) {
          perror("Failed to open input");
//          exit(1);
      }

	return fp;
}

FILE* getOutFP(int fdout)
{
   FILE *fpout  = {0};
  	
  	if(fdout==1){
  		fpout = stdout;
  	} else {
  
  		fpout = fdopen(fdout,"wb");

  		if (fpout == 0) {
//  		    fprintf(stderr, "Error - Cannot open output stream %d\n", fdout);
// 		    fprintf(stderr, "Program will close unexpectedly\n");
  		    exit(-1);
  		}
  	}
	
	return fpout;
}

int base64_decode(int infd, int outfd)
{

	// esto hacerlo aca en la funcion y retornar el codigo de error correspondiente en caso de fallo
	FILE *fp = getInFP(infd);
	FILE *fpout = getOutFP(outfd);

	int count=0;
	int buffer[4] = {0};  /* initialized to zeroes */
	char bufferDecoded[4] = {0};
    int i, rc;

	while(rc != EOF){
		// leo 4 bytes
		i=0;
	 	
		if(count==76){ //remuevo el salto de linea
			getc(fp);
			count=0;
		}

    	for (i = 0; i < 4 && (rc = getc(fp)) != EOF; buffer[i++] = rc) {}

		if(i>0){
			int a = doDecode(buffer, bufferDecoded, i);

			if(a<0){
				//error
				return a * (-1);
			}

			count += 4;

			for(i=0;i<a;i++){
				putc(bufferDecoded[i], fpout);
			}
		}	
	}

	fclose(fpout);
	return 0;//todo ok
}



int doEncode(int source[], char encoded[], int amount)
{
	int i, temp[4];

	// seteo todo temp en 0
	for(i=0; i < amount; i++){ 
		temp[i]=0;
	}
	// agrego 0 a la derecha
	for(i=amount; i<3; i++){source[i]=0;}
	
	temp[0] = source[0] >> 2;
	temp[0] &= 63;

	//posiciono los dos ultimos bits de source[0] en bits 2 y 3 de encoded[]
	temp[1] = 63 & (source[0] << 4);
	//copio los primeros 4 bits de source[2] en los ultimos lugares de encoded[1]
	temp[1] |= (63 & (source[1] >> 4));

	//posiciono los ultimos 4 bits de source[1] en las posiciones 2, 3, 4 y 5 de encoded[2]
	temp[2] = 63 & (source[1] << 2); 
	temp[2] |= 63 & (source[2] >> 6);


	temp[3] = 63 & source[2];//elimino los dos bit mas significativos y copio el resto como ultimo encoded[3]

	// mapeo los valores
	for(i=0;i<4;i++){
		if(temp[i]<=25 ){
			encoded[i] = (temp[i]+65);
		} else if(temp[i] <= 51) {
			encoded[i] = (temp[i]+97-26);			
		} else if(temp[i]<=61){
			encoded[i] = (temp[i]+48-52);	
		} else if(temp[i]==62){
			encoded[i] = 43;//'+'
		} else if(temp[i]==63){
			encoded[i] = 47;//'/'
		}
	}
	
	//padding
	if(amount==1){
		encoded[2]='=';
		encoded[3]='=';
	} else if(amount==2){
		encoded[3]='=';	
	}

	return 0;
}

int base64_encode(int infd, int outfd)
{
	FILE *fp = getInFP(infd);
	FILE *fpout = getOutFP(outfd);

	int count=0; 
	int buffer[3] = {0};  /* initialized to zeroes */
	char bufferEncoded[3] = {0};
    int i, rc;


	while(rc != EOF){
		// leo 3 bytes
    	for (i = 0; i < 3 && (rc = getc(fp)) != EOF ; buffer[i++] = rc) {}
		if(i>0){

			int a = doEncode(buffer, bufferEncoded, i);

			if(a!=0){
				//error
				return a;
			}
			
			if(count >= 76){
				putc('\n', fpout);
				count=0;
			}

			fwrite(bufferEncoded,1,4,fpout);						
			
			count += 4;
		}		
	}

	fclose(fpout);
	return 0;//todo ok
}

